(ns actors)

;; utils
;(defn rand-nth [coll]
;  (nth coll (rand-int (count coll))))

;; definitions
(def magic-cost 10)

;; player attributes
(defstruct attributes :str :dex :con :int :will)

;; a player (or a enemy)
(defstruct player-struct :name :class :attributes :level :current-life :max-life :current-mp :max-mp :exp)

;; xp related funcs
(def xp-table (vec (map #(* %1 %1 150) (range 1 21))))

(defn- to-next-level [level]
  (xp-table (dec level)))

(defn- xp-gained-for-level [level]
  (* level level (- 30 level)))

(defn- leveled? [level xp]
  (<= (to-next-level level) xp))

;; multimethod to create players
(defn- new-player-base [name clazz level st dex con intl will life mp]
  (struct player-struct
	  name, clazz,
	  (struct attributes (ref st), (ref dex), (ref con), (ref intl), (ref will)),
	  (ref level), (ref life), (ref life), (ref mp), (ref mp), (ref 0)))

(defmulti new-player (fn [_ clazz _] clazz))

(defmethod new-player :Warrior [name clazz level]
  (new-player-base name clazz level 10 6 8 2 4 18 0))

(defmethod new-player :Cleric [name clazz level]
  (new-player-base name clazz level 8 4 6 2 10 16 10))

(defmethod new-player :Rogue [name clazz level]
  (new-player-base name clazz level 8 10 6 4 2 14 10))

(defmethod new-player :Mage [name clazz level]
  (new-player-base name clazz level 4 8 6 10 2 12 20))

;; attributes acessors
(defn strength [player] (:str (:attributes player)))
(defn dexterity [player] (:dex (:attributes player)))
(defn constitution [player] (:con (:attributes player)))
(defn inteligence [player] (:int (:attributes player)))
(defn will [player] (:will (:attributes player)))

(defn critical [player] (* 2 @(dexterity player)))
(defn evade [player] @(dexterity player))

(defn base-damage
  "base damage is a range from strength to (strength + half the player level)"
  [player]
  (let [st @(strength player),
        level @(:level player)]
    (range st (inc (+ st (/ level 2))))))

(defn damage [player] (rand-nth (base-damage player)))

(defn caster? [{clazz :class}] (or (= clazz :Cleric) (= clazz :Mage)))

(defn can-cast? [player] (> @(:current-mp player) magic-cost))

(defn low-life? [player] (< @(:current-life player) (/ @(:max-life player) 2)))

(defn dead? [player] (<= @(:current-life player) 0))

(defn player-leveled? [{l :level xp :exp}] (and (< @l 20) (leveled? @l @xp)))

(defn magic-damage
  "magic damage is equals 2 * inteligence + level"
  [player] (+ (* 2 @(inteligence player)) @(:level player)))

(defn increase-life
  "every step a player takes his's life increases by 'level'
but not more than his max life"
  [player]
  (let [l (:current-life player),
	ml (:max-life player)]
    (if (< @l @ml)
      (dosync (alter l + (min @(:level player) (- @ml @l)))))))

(defn increase-mp
  "every step a player takes his's mp increases by 'level'
but not more than his max mp"
  [player]
  (let [m (:current-mp player),
	mm (:max-mp player)]
    (if (< @m @mm)
      (dosync (alter m + (min @(:level player) (- @mm @m)))))))

(defn give-xp [player xp] (swap! (:exp player) + xp))

(defn- cause-damage [player dmg] (dosync (alter (:current-life player) - dmg)))

(defn attack
  "attack an enemy and cause damage to him"
  [player enemy]
  (let [crit (< (rand-int 100) (critical player)), ;; critical hit?
        evd (< (rand-int 100) (evade enemy)), ;; enemy evaded?
        dmg (if crit
              (* 2 (damage player)) ;; calculate damage
              (damage player))]
    (if (not evd) (cause-damage enemy dmg))
    {:damage dmg :critical crit :evade evd}))

;; list game classes
(defn list-classes []
  {:Warrior "High damage, life",
   :Rogue "High damage, critical, evade. Low life",
   :Mage "High evade. Low damage, life. Magic",
   :Cleric "Average damage, life. Heal"})

;; level up
(defn- base-level-up [player]
  (alter (:level player) inc)
  (alter (:max-life player) + @(constitution player)) ;; max life = max life + constitution
  (ref-set (:current-life player) @(:max-life player)) ;; full life =)
  (ref-set (:exp player) 0))

(defmulti level-up (fn [player] (:class player)))

(defmethod level-up :Warrior [player]
  (dosync 
   (alter (strength player) inc)
   (when (even? @(:level player))
     (alter (constitution player) inc)
     (alter (dexterity player) inc))
   (base-level-up player)))

(defmethod level-up :Rogue [player]
  (dosync
   (alter (dexterity player) inc)
   (when (even? @(:level player))
     (alter (strength player) inc)
     (alter (constitution player) inc))
   (base-level-up player)))
    
(defmethod level-up :Cleric [player]
  (dosync
   (alter (:max-mp player) + @(will player))
   (ref-set (:current-mp player) @(:max-mp player))
   (alter (will player) inc)
   (when (even? @(:level player))
     (alter (strength player) inc)
     (alter (constitution player) inc))
   (base-level-up player)))

(defmethod level-up :Mage [player]
  (dosync
   (alter (:max-mp player) + @(inteligence player))
   (ref-set (:current-mp player) @(:max-mp player))
   (alter (inteligence player) inc)
   (when (even? @(:level player))
     (alter (dexterity player) inc)
     (alter (constitution player) inc))
   (base-level-up player)))

;; magics
(defn heal [{life :current-life mlife :max-life mp :current-mp :as player}]
  (when (can-cast? player)
    (dosync (alter life + (min @(will player) (- @mlife @life)))
	    (alter mp - magic-cost))))

(defn fireball [player enemy]
  (when (can-cast? player)
    (dosync
     (alter (:current-life enemy) - @(inteligence player))
     (alter (:current-mp player) - magic-cost))))